package com.zycfc.zsf.boot.util.fee;

import com.zycfc.zsf.boot.util.collection.*;
import java.math.*;
import java.util.*;

public class FeeIrrUtils
{
    public static final double MINDIF = 1.0E-8;
    
    public static BigDecimal calcIrrRateForSbSy(final List<FeeInfo> feeList, final Integer tnr, BigDecimal dnAmt, final String lastInd, final List<BigDecimal> shdList) {
        if (ListUtils.isEmpty(feeList)) {
            return null;
        }
        BigDecimal firstTermFeeAmt = BigDecimal.ZERO;
        BigDecimal termFeeAmt = BigDecimal.ZERO;
        BigDecimal lastTermFeeAmt = BigDecimal.ZERO;
        BigDecimal feeAmt = BigDecimal.ZERO;
        BigDecimal irrLastTremFeeAmt = BigDecimal.ZERO;
        BigDecimal irrTremFeeAmt = BigDecimal.ZERO;
        BigDecimal sbsyDnAmt;
        dnAmt = (sbsyDnAmt = getZeroBigDecimalIfNull(dnAmt));
        for (final FeeInfo fee : feeList) {
            if (!fee.isSbsyFlag()) {
                return null;
            }
            if (!"CC".equals(fee.getCollType())) {
                continue;
            }
            feeAmt = amtMult(dnAmt, getZeroBigDecimalIfNull(fee.getFeeRate()));
            feeAmt = getFeeAmt(feeAmt, fee);
            if (fee.isSbsyFlag()) {
                sbsyDnAmt = dnAmt.subtract(feeAmt);
            }
            if ("N".equals(lastInd)) {
                BigDecimal monFeeRate = null;
                if (null != fee.getMonFeeRate() && fee.getMonFeeRate().compareTo(BigDecimal.ZERO) > 0) {
                    monFeeRate = fee.getMonFeeRate();
                }
                else {
                    monFeeRate = fee.getFeeRate().divide(BigDecimal.valueOf(tnr), 10, RoundingMode.HALF_UP);
                }
                termFeeAmt = dnAmt.multiply(monFeeRate).setScale(2, RoundingMode.HALF_UP);
            }
            else {
                termFeeAmt = div(feeAmt, new BigDecimal(tnr));
            }
            irrTremFeeAmt = irrTremFeeAmt.add(termFeeAmt);
            firstTermFeeAmt = firstTermFeeAmt.add(termFeeAmt);
            if ("N".equals(lastInd)) {
                lastTermFeeAmt = termFeeAmt;
            }
            else {
                lastTermFeeAmt = feeAmt.subtract(termFeeAmt.multiply(new BigDecimal(tnr - 1)));
            }
            irrLastTremFeeAmt = irrLastTremFeeAmt.add(lastTermFeeAmt);
        }
        final List<Double> feeAmtList = new ArrayList<Double>(tnr + 1);
        feeAmtList.add(sbsyDnAmt.doubleValue() * -1.0);
        if (ListUtils.isNotEmpty(shdList)) {
            for (int i = 0; i < shdList.size(); ++i) {
                feeAmtList.add(shdList.get(i).doubleValue());
            }
        }
        else {
            BigDecimal lastTermAmt = dnAmt;
            final BigDecimal termDnAmt = div(dnAmt, new BigDecimal(tnr + ""));
            for (int j = 0; j < tnr - 1; ++j) {
                feeAmtList.add(termDnAmt.doubleValue());
                lastTermAmt = lastTermAmt.subtract(termDnAmt);
            }
            feeAmtList.add(lastTermAmt.doubleValue());
        }
        final Double irrTermRate = getIrr(feeAmtList);
        final BigDecimal irrRate = new BigDecimal(irrTermRate + "").multiply(new BigDecimal(12)).setScale(9, RoundingMode.HALF_UP);
        return irrRate;
    }
    
    public static BigDecimal calcIrrRate(final List<FeeInfo> feeList, final Integer tnr, BigDecimal dnAmt, final String lastInd) {
        if (ListUtils.isEmpty(feeList)) {
            return null;
        }
        BigDecimal firstTermFeeAmt = BigDecimal.ZERO;
        BigDecimal termFeeAmt = BigDecimal.ZERO;
        BigDecimal lastTermFeeAmt = BigDecimal.ZERO;
        BigDecimal feeAmt = BigDecimal.ZERO;
        BigDecimal irrLastTremFeeAmt = BigDecimal.ZERO;
        BigDecimal irrTremFeeAmt = BigDecimal.ZERO;
        dnAmt = getZeroBigDecimalIfNull(dnAmt);
        for (final FeeInfo fee : feeList) {
            if ("FC".equals(fee.getCollType())) {
                feeAmt = amtMult(dnAmt, getZeroBigDecimalIfNull(fee.getFeeRate()));
                feeAmt = getFeeAmt(feeAmt, fee);
                firstTermFeeAmt = firstTermFeeAmt.add(feeAmt);
            }
            if ("CC".equals(fee.getCollType())) {
                feeAmt = amtMult(dnAmt, getZeroBigDecimalIfNull(fee.getFeeRate()));
                feeAmt = getFeeAmt(feeAmt, fee);
                if ("N".equals(lastInd)) {
                    BigDecimal monFeeRate = null;
                    if (null != fee.getMonFeeRate() && fee.getMonFeeRate().compareTo(BigDecimal.ZERO) > 0) {
                        monFeeRate = fee.getMonFeeRate();
                    }
                    else {
                        monFeeRate = fee.getFeeRate().divide(BigDecimal.valueOf(tnr), 10, RoundingMode.HALF_UP);
                    }
                    termFeeAmt = dnAmt.multiply(monFeeRate).setScale(2, RoundingMode.HALF_UP);
                }
                else {
                    termFeeAmt = div(feeAmt, new BigDecimal(tnr));
                }
                irrTremFeeAmt = irrTremFeeAmt.add(termFeeAmt);
                firstTermFeeAmt = firstTermFeeAmt.add(termFeeAmt);
                if ("N".equals(lastInd)) {
                    lastTermFeeAmt = termFeeAmt;
                }
                else {
                    lastTermFeeAmt = feeAmt.subtract(termFeeAmt.multiply(new BigDecimal(tnr - 1)));
                }
                irrLastTremFeeAmt = irrLastTremFeeAmt.add(lastTermFeeAmt);
            }
        }
        final List<Double> feeAmtList = new ArrayList<Double>(tnr + 1);
        feeAmtList.add(dnAmt.doubleValue() * -1.0);
        final BigDecimal termDnAmt = div(dnAmt, new BigDecimal(tnr + ""));
        BigDecimal lastTermAmt = dnAmt;
        feeAmtList.add(firstTermFeeAmt.add(termDnAmt).doubleValue());
        lastTermAmt = lastTermAmt.subtract(termDnAmt);
        for (int i = 1; i < tnr - 1; ++i) {
            feeAmtList.add(irrTremFeeAmt.add(termDnAmt).doubleValue());
            lastTermAmt = lastTermAmt.subtract(termDnAmt);
        }
        feeAmtList.add(irrLastTremFeeAmt.add(lastTermAmt).doubleValue());
        final Double irrTermRate = getIrr(feeAmtList);
        final BigDecimal irrRate = new BigDecimal(irrTermRate + "").multiply(new BigDecimal(12)).setScale(9, RoundingMode.HALF_UP);
        return irrRate;
    }
    
    public static BigDecimal amtMult(final BigDecimal left1, final BigDecimal left2) {
        return left1.multiply(left2).setScale(2, RoundingMode.HALF_UP);
    }
    
    public static BigDecimal div(final BigDecimal left1, final BigDecimal left2) {
        final BigDecimal dec3 = left1.divide(left2, 2, RoundingMode.HALF_UP);
        return dec3;
    }
    
    public static BigDecimal getZeroBigDecimalIfNull(final BigDecimal input) {
        return (input == null) ? BigDecimal.ZERO : input;
    }
    
    public boolean isAmtGreatThanZero(BigDecimal amt) {
        if (null == amt) {
            amt = BigDecimal.ZERO;
        }
        return amt.compareTo(BigDecimal.ZERO) > 0;
    }
    
    public static BigDecimal getFeeAmt(BigDecimal feeAmt, final FeeInfo feeInfo) {
        if (null != feeInfo.getMinFeeAmt() && feeAmt.compareTo(feeInfo.getMinFeeAmt()) < 0) {
            feeAmt = feeInfo.getMinFeeAmt();
            return feeAmt;
        }
        if (null != feeInfo.getMaxFeeAmt() && feeAmt.compareTo(feeInfo.getMaxFeeAmt()) > 0) {
            feeAmt = feeInfo.getMaxFeeAmt();
            return feeAmt;
        }
        return feeAmt;
    }
    
    public static double getIrr(final List<Double> cashFlow) {
        int LOOPNUM = 1000;
        final double flowOut = cashFlow.get(0);
        double minValue = 0.0;
        double maxValue = 1.0;
        double testValue = 0.0;
        while (LOOPNUM > 0) {
            testValue = (minValue + maxValue) / 2.0;
            final double npv = NPV(cashFlow, testValue);
            if (Math.abs(flowOut + npv) < 1.0E-8) {
                break;
            }
            if (Math.abs(flowOut) > npv) {
                maxValue = testValue;
            }
            else {
                minValue = testValue;
            }
            --LOOPNUM;
        }
        return testValue;
    }
    
    public static double NPV(final List<Double> flowInArr, final double rate) {
        double npv = 0.0;
        for (int i = 1; i < flowInArr.size(); ++i) {
            npv += flowInArr.get(i) / Math.pow(1.0 + rate, i);
        }
        return npv;
    }
    
    public static void main(final String[] args) {
        final FeeInfo fee = new FeeInfo("CC", null, new BigDecimal("0.01"));
        final List<FeeInfo> feeList = new ArrayList<FeeInfo>();
        feeList.add(fee);
        final BigDecimal irrRate = calcIrrRate(feeList, 12, new BigDecimal("12000"), "N");
        System.out.println("irrRate=" + irrRate.toPlainString());
    }
    
    public static class FeeInfo
    {
        public String collType;
        public BigDecimal feeRate;
        public BigDecimal monFeeRate;
        public BigDecimal maxFeeAmt;
        public BigDecimal minFeeAmt;
        public boolean sbsyFlag;
        
        public void setMaxFeeAmt(final BigDecimal maxFeeAmt) {
            this.maxFeeAmt = maxFeeAmt;
        }
        
        public BigDecimal getMaxFeeAmt() {
            return this.maxFeeAmt;
        }
        
        public void setMinFeeAmt(final BigDecimal minFeeAmt) {
            this.minFeeAmt = minFeeAmt;
        }
        
        public BigDecimal getMinFeeAmt() {
            return this.minFeeAmt;
        }
        
        public void setCollType(final String collType) {
            this.collType = collType;
        }
        
        public String getCollType() {
            return this.collType;
        }
        
        public void setFeeRate(final BigDecimal feeRate) {
            this.feeRate = feeRate;
        }
        
        public BigDecimal getFeeRate() {
            return this.feeRate;
        }
        
        public void setMonFeeRate(final BigDecimal monFeeRate) {
            this.monFeeRate = monFeeRate;
        }
        
        public BigDecimal getMonFeeRate() {
            return this.monFeeRate;
        }
        
        public void setSbsyFlag(final boolean sbsyFlag) {
            this.sbsyFlag = sbsyFlag;
        }
        
        public boolean isSbsyFlag() {
            return this.sbsyFlag;
        }
        
        public FeeInfo(final String collType, final BigDecimal feeRate, final BigDecimal maxFeeAmt, final BigDecimal minFeeAmt, final Boolean sbsyFlag) {
            this.sbsyFlag = false;
            this.collType = collType;
            this.feeRate = feeRate;
            this.maxFeeAmt = maxFeeAmt;
            this.minFeeAmt = minFeeAmt;
            this.sbsyFlag = (sbsyFlag != null && sbsyFlag);
        }
        
        public FeeInfo(final String collType, final BigDecimal feeRate, final BigDecimal maxFeeAmt, final BigDecimal minFeeAmt) {
            this.sbsyFlag = false;
            this.collType = collType;
            this.feeRate = feeRate;
            this.maxFeeAmt = maxFeeAmt;
            this.minFeeAmt = minFeeAmt;
        }
        
        public FeeInfo(final String collType, final BigDecimal feeRate, final BigDecimal monFeeRate) {
            this.sbsyFlag = false;
            this.collType = collType;
            this.feeRate = feeRate;
            this.monFeeRate = monFeeRate;
        }
    }
}
